from Core.Portfolio import Portfolio
import pandas as pd


class Portfolio2(object):
    def __init__(self, name, quote_agent=None):
        # print("Create Portfolio " + name)
        self._name = name
        # Equity / Margin / Position Value
        self._equity = 0
        # notional / exposure / net exposure
        # Future exposure = position.asset.price_multiplier
        self._notional = 0
        #
        self._cash = 0
        # Cash + Equity
        self._value = 0
        #
        self._positionPL = 0
        #
        self._closedPL = 0
        #
        self._longEquity = 0
        self._longNotional = 0
        self._shortEquity = 0
        self._shortNotional = 0
        self._grossNotional = 0
        #
        self._returns = 0
        self._unitNetValue = 1
        #
        self._deposit = 0
        self._withdrawal = 0
        self._latestWeekly = 0
        self._latestMonthly = 0
        self._compoundAnnualReturn = 0
        #
        self._tradingCost = 0
        #
        # self.positions = []
        self.positions_by_symbol = {}
        self.trades = []
        self.accounts = []
        self._datetime1 = None
        self._datetime2 = None
        #
        self._createDateTime = None
        self._print_info = False

        # ---如何获取报价---
        # if database != None:
        #    self.Save()
        self.quote_agent = quote_agent

        # ---Performance Tracker---
        self._fields = ["Date", "Cash", "Equity", "Notional", "ProfitLoss", "Value", "Returns"]
        self._df_performances = pd.DataFrame(columns=self._fields)
        self._dataCache = {}

        #
        self._dfTrades = pd.DataFrame(columns=["Symbol", "DateTime1", "DateTime2", "Equity"])

    #
    def deposit(self, money, tradeDateTime):
        #
        qty = money
        if qty >= 0:
            self._deposit += qty
        else:
            self._withdrawal += abs(qty)
        #
        self._cash = self._cash + qty
        self._value = self._equity + self._cash
        self._datetime2 = tradeDateTime

    def Position(self, symbol):
        return self.positions_by_symbol.get(symbol)

    def buy(self, symbol, price, qty, tradeDateTime, adjFactor=1):
        self.AddTrade(symbol, price, qty, "Buy", tradeDateTime, adjFactor)

    def Sell(self, symbol, price, qty, tradeDateTime, adjFactor=1):
        self.AddTrade(symbol, price, qty, "Sell", tradeDateTime, adjFactor)

    def Short(self, symbol, price, qty, tradeDateTime, adjFactor=1):
        self.AddTrade(symbol, price, qty, "Short", tradeDateTime, adjFactor)

    def Cover(self, symbol, price, qty, tradeDateTime, adjFactor=1):
        self.AddTrade(symbol, price, qty, "Cover", tradeDateTime, adjFactor)

    # ---Add Mutiples Trade/Fills---
    # fills[{Symbol，Price，Qty，Side}] Side=Buy / Sell / Short / Cover / Deposit / Withdraw
    def AddTrades(self, trades, tradeDateTime):
        for trade in trades:
            self.AddTrade(symbol=trade["Symbol"], price=trade["Price"], qty=trade["Qty"], side=trade["Side"],
                          tradeDateTime=tradeDateTime, adjFactor=trade["AdjFactor"], print_info=self._print_info)

    # ---Qty -> Positive
    # ---Amount  ->Can be Negtive
    def AddTrade(self, symbol, price, qty, side, tradeDateTime, adjFactor=1, saveHistory=True, print_info=False):
        # 市价（Close）买进
        if price < 0:
            quote = self.quote_agent.Get_Quote(symbol, tradeDateTime)
            if not quote:
                print("Can't Trade with Price=0, No Quote", symbol, tradeDateTime)
            #
            price = quote["Close"]
        #
        if print_info:
            print(symbol + " " + side + " Price:" + str(price) + " Qty:" + str(qty) + " @" + str(tradeDateTime))
        #
        if self._datetime1 == None:
            self._datetime1 = tradeDateTime
        #
        tradeDoc = {}
        tradeDoc["Symbol"] = symbol
        tradeDoc["Portfolio"] = self._name
        tradeDoc["Price"] = price
        tradeDoc["Qty"] = qty
        tradeDoc["Side"] = side
        tradeDoc["StdDateTime"] = tradeDateTime
        #
        # localDateTime = Gadget.ToLocalDateTime(tradeDateTime) # 20200927 为同花顺数据库适配
        localDateTime = tradeDateTime
        tradeDoc["Key"] = self._name + "_" + symbol + "_" + side + "_" + Gadget.ToDateTimeString(localDateTime)

        if saveHistory:
            self.trades.append(tradeDoc)

        # 确认交易成本
        if self._tradingCost:
            tradingCost = self._tradingCost.get(symbol)
            if not tradingCost:
                tradingCost = 0
        else:  # 未指定交易成本管理器
            tradingCost = 0

        # 存钱
        if side == "Deposit":
            if qty >= 0:
                self._deposit += qty
            else:
                self._withdrawal += abs(qty)

            self._cash = self._cash + qty
            self._value = self._equity + self._cash
            tradeDoc["CashFlow"] = qty

        else:  # 交易
            # ---if new Position, Create Cache---
            if symbol not in self.positions_by_symbol:
                position = {}
                position["Symbol"] = symbol
                position["Cost"] = 0
                position["Qty"] = 0
                position["Amount"] = 0
                position["Price"] = 0  # LastTrade
                position["Value"] = 0  # new
                position["CashFlow"] = 0  # new
                position["PL"] = 0  # new
                position["Available"] = 0
                position["Portfolio"] = self._name
                position["AdjFactor"] = adjFactor
                position["EntryDateTime"] = tradeDateTime
                self.positions_by_symbol[symbol] = position

            position = self.positions_by_symbol[symbol]
            position["StdDateTime"] = tradeDateTime
            position["UpdateDateTime"] = tradeDateTime

            # Find Adj Factor
            # closingDateTime = Gadget.ToClosingDateTime(tradeDateTime)
            # quote = GetQuote(self.database, symbol, closingDateTime)
            # if quote == None:
            #     print("Portfolio::Update Can't Value Position " + symbol + " No Quote")

            # ---update adjusted factor && position qty BEFORE trade---
            # self.CorrectAdjFactor(quote["AdjFactor"], position)

            # ---Amount and Cost Price---
            if side == "Buy":
                # Update Cost
                position["Cost"] = (position["Cost"] * position["Qty"] + price * qty) / (position["Qty"] + qty)

                # Update Position---
                position["Amount"] += qty
                # position["Qty"] = position["Qty"] + qty
                cashflow = -1 * price * (1 + tradingCost) * qty

            elif side == "Sell":
                # qty = math.fabs(qty)
                # if qty > position["Qty"]:
                #    qty = position["Qty"]
                position["Amount"] -= qty
                # position["Qty"] = position["Qty"] - qty
                cashflow = price * (1 - tradingCost) * qty

            elif side == "Short":
                #
                position["Amount"] -= qty
                position["Cost"] = (position["Cost"] * position["Qty"] + price * qty) / (position["Qty"] + qty)
                cashflow = price * (1 - tradingCost) * qty

            elif side == "Cover":
                position["Amount"] += qty
                cashflow = -1 * price * (1 + tradingCost) * qty

            # ---Update Cashflow---
            # cashflow = price * (1 - tradingCost) * qty
            # if fill["Side"] == "Buy" or "Cover":
            #    cashflow = -1 * price * (1 + tradingCost) * qty
            self._cash = self._cash + cashflow
            tradeDoc["CashFlow"] = cashflow

            # update 建仓比例，不计市值变化的
            position["Ratio"] = position["Cost"] * position["Amount"] / self._value

            # ---Amount To Position---
            position["Qty"] = abs(position["Amount"])
            if position["Amount"] >= 0:
                position["Side"] = "Long"
            elif position["Amount"] < 0:
                position["Side"] = "Short"

            # cumulative cashflow
            position["CashFlow"] = position["CashFlow"] + cashflow

            # update position value
            # position["Value"] = position["Qty"] * price
            position["Equity"] = position["Amount"] * price  # Position Value
            position["Notional"] = position["Equity"]

            # update pl = position value + cum cashflow
            # position["PL"] = position["Value"] + position["CashFlow"]

            #
            position["Price"] = price

            # Remove if No Position
            if position["Qty"] == 0:
                self.positions_by_symbol.pop(symbol)

            # self.updateDatetime = tradeDateTime
            # ---Finish to add/remove position, not Re-Valuation whole Portfolio yet---
        #
        # self.Update(tradeDateTime)
        #
        # trade_log = pd.DataFrame(data=[[date2, self._cash, self._equity, self._notional, self._positionPL, self._value, 0]],
        #                               columns=self._fields)
        # self._dfTrades = pd.concat([self._dfTrades, trade_log], axis=0)

    # ---Summation of the Positions---
    def update_performance(self, updateDateTime):
        #
        position_equity = 0
        position_notional = 0
        longEquity = 0
        longNotional = 0
        shortEquity = 0
        shortNotional = 0
        profitloss = 0

        # aaa = 0
        # if updateDateTime >= datetime.datetime(2023,5,17):
        #     aaa = 1

        # ---Loop Positions---
        for position in self.positions_by_symbol.values():
            price = position["Price"]

            # ---Equity and Notional---
            position["Equity"] = position["Amount"] * price  # Position Value

            # ---Stock: Equity==Notional---
            position["Notional"] = position["Equity"]

            #
            if position["Amount"] >= 0:
                longEquity += position["Equity"]
                longNotional += position["Notional"]
                position_notional += position["Notional"]
            else:
                shortEquity += position["Equity"]
                shortNotional += position["Notional"]
                position_notional -= position["Notional"]

            position_equity += position["Equity"]
            # if aaa == 1:
            #     print(position["Symbol"], position_equity)

            # ---Profit Loss---
            position["ProfitLoss"] = (price - position["Cost"]) * position["Amount"]
            profitloss += position["ProfitLoss"]

            # ---backup---
            # position value
            # position["Value"] = price * position["Qty"]
            # backup: position value + cum Cashflow
            # position["PL"] = position["Value"] + position["CashFlow"]
            # profitloss2 += position["PL"]

            # ---datetime---
            # position["StdDateTime"] = updateDateTime
            # print(symbol + "," + str(endDateTime) + "," + str(position["Qty"]) + "," +  str(position["Price"]) + "," + str(position["Equity"]))

        #
        self._notional = position_notional
        self._equity = position_equity
        self._positionPL = profitloss
        self._value = self._equity + self._cash
        # print("Portfolio::self._value", self._value, updateDateTime)
        #
        self._longEquity = longEquity
        self._longNotional = longNotional
        self._shortEquity = shortEquity
        self._shortNotional = shortNotional
        #
        self._grossNotional = self._longNotional + self._shortNotional
        gross_leverage = self._grossNotional / self._value
        net_leverage = self._notional / self._value
        #
        self._datetime2 = updateDateTime

        # 耗时 20200919 为Rap项目屏蔽，performance 在 strategy algorithm 下的 performance tracker 有跟踪
        date2 = self._datetime2.date()

        # 创建一个新的DataFrame（或Series），作为要添加的行
        # new_row = pd.DataFrame({'A': [5], 'B': [6]}, index=[3])
        df_new_performances = pd.DataFrame(data=[[date2, self._cash, self._equity, self._notional, self._positionPL, self._value, 0]],
                                      columns=self._fields)

        # 将新行添加到原有DataFrame中
        # 老方法
        # self._df_performances = pd.concat([self._df_performances, df_new_performances], axis=0)
        # 新方法
        self._df_performances = self._df_performances.append(df_new_performances, ignore_index=True)

    # ---Re Valuate the whole Portfolio, (Valuate each position)---
    # 可以指定数据库源，也可以使用内部数据源
    def update(self, updateDateTime, quote_agent):

        self.update_positions_value(updateDateTime, quote_agent)
        # ---Valuate Account 汇总整个账户---
        self.update_performance(updateDateTime)
        # ---Add to Accounts---
        # accountDoc = self.GenerateAccountDocument()
        # self.accounts.append(accountDoc)

    # ---Valuation: Use Position to Calc Portfolio Value(Account)---
    # 计算组合价值，更新每个仓位的估值，需要最新价格，
    def update_positions_value(self, updateDateTime, quote_agent):
        # ---Valuation: Use Position to Calc Portfolio Value(Account)， 估值每个仓位---
        ValuatePositions(quote_agent, self.positions_by_symbol.values(), updateDateTime)

        # ---Valuate each Position---
        for position in positions:
            symbol = position["Symbol"]

            # ---Get Quote---
            quote = quote_agent.Get_Quote(symbol, updateDateTime)
            #
            if quote == None:
                print("Portfolio::Update Can't Value Position " + symbol + " No Quote")
                continue

            # update price
            price = quote["Close"]
            position["Price"] = price

            # update adjusted factor && position qty
            if position["Qty"] != 0 and "AdjFactor" in quote:
                adjFactor = quote["AdjFactor"]
                if adjFactor == None:
                    adjFactor = 1
                if position["AdjFactor"] == None:
                    position["AdjFactor"] = 1
                CorrectPositionWithAdjFactor(adjFactor, position)

            # ---Equity and Notional---
            position["Equity"] = position["Amount"] * price  # Position Value
            position["Notional"] = position["Equity"]

            # ---Profitloss---
            position["ProfitLoss"] = (price - position["Cost"]) * position["Amount"]

            # ---position value---
            # position["Value"] = price * position["Qty"]
            # backup: position value + cum Cashflow
            # position["PL"] = position["Value"] + position["CashFlow"]
            # profitloss2 += position["PL"]

            # ---datetime---
            position["StdDateTime"] = updateDateTime
            # print(symbol + "," + str(endDateTime) + "," + str(position["Qty"]) + "," +  str(position["Price"]) + "," + str(position["Equity"]))

    def GenerateAccountDocument(self):
        accountDoc = ToAccount(self)
        return accountDoc

    # ---Sell Old/ Buy new-->Cast to Target Postitions---
    # targetPositions[{Symbol，Money}] #Side=Long/Short
    # 有权重按照权重，没有权重，平均持仓
    def Rebalance(self, quoteAgent, targetPositions, tradeDateTime):
        print("Rebalence: " + self._name + " @" + str(tradeDateTime))

        # closingDateTime = Gadget.ToClosingDateTime(tradeDateTime)
        trades = RebalancePosition(self, targetPositions, tradeDateTime, quoteAgent)
        self.AddTrades(trades, tradeDateTime)

    def Summary(self, postions=False, accounts=False, trades=False):
        print("Pf Summary", self._name + " " + str(self._datetime2)
              + " Unit: %.4f, Value: %.4f, Cash: %.4f, Equity: %.4f, PosiPL: %.4f, ClosePL: %.4f"
              % (self._unitNetValue, self._value, self._cash, self._equity, self._positionPL, self._closedPL)
              )
        #
        if postions:
            self.PrintPositions()
        # print("***")

    def PrintPositions(self):
        print("Total #Positions", len(self.positions_by_symbol))
        print("Symbol,Qty,Price,Value,CashFlow,PL")
        for k, v in self.positions_by_symbol.items():
            print(k, "Qty", str(v["Qty"]), "Price", str(v["Price"]), "Value", str(v["Value"]), "CashFlow",
                  str(v["CashFlow"]), "PL", str(v["PL"]))

    def PrintAccounts(self):
        for account in self.accounts:
            print(str(account["StdDateTime"]),
                  "Unit:" + str(account["UnitNetValue"]) + " Value:" + str(account["Value"]) + " Cash:" + str(
                      account["Cash"]) + " Equity:" + str(account["Equity"]) \
                  + " Profitloss:" + str(account["ProfitLoss"]) + " Profitloss2:" + str(account["ProfitLoss2"]))

